﻿// ArduinoFreeRotator
// 
// Arduino free stepper motor rotation 
// 
// When a rotation step is triggered, ArduinoStepperRotator simply sends out
// an integer number (encoded as character) via the serial port. 
// The corresponding Arduino program listens to the serial port. When Arduino 
// receives a character, it moves the stepper motor for the transmitted
// number of steps.
// 
// FLMScan - Featured by FabLab Munich

using System;
using System.Threading;
using System.IO.Ports;
using System.ComponentModel;
using log4net;
using FLMScan.Exception;
using FLMScan.Serial;

namespace FLMScan.Rotator
{
    class ArduinoFreeRotator : IDevice, IFreeRotator
    {
        private ILog logger;
        private bool isDisposed = false;
        private bool isConnected = false;        
        
        private const int DefaultMaxPosition = 200;

        private int serialPortIndex = 1;
        private int maxStepCount;
        private SerialPortWrapper serialPortWrapper;        
        private int maxPosition = DefaultMaxPosition;
        private int minPosition = 1;
        private int currentPosition = 1;

        private BackgroundWorker backgroundWorker;

        private const string SerialPortNamePrefix = "COM";
        private const string LogArduinoDeviceInitializationSucceeded = "Arduino initialization succeeded. Connected COM port ";
        private const string LogArduinoFreeRotatorStepFailed = "Stepping Arduino free rotator failed. Arduino was probably disconnected.";                                

        event EventHandler StepDone;

        event EventHandler IFreeRotator.StepDone
        {
            add { StepDone += value; }
            remove { StepDone -= value; }
        }

        public ArduinoFreeRotator(int serialPortIndex, int maxStepCount)
        {
            logger = LogManager.GetLogger(typeof(ArduinoFreeRotator));
            this.serialPortIndex = serialPortIndex;
            this.maxStepCount = maxStepCount;

            backgroundWorker = new BackgroundWorker();
            backgroundWorker.DoWork += new DoWorkEventHandler(DoRotation);
            backgroundWorker.RunWorkerCompleted += WorkCompleted;                
        }

        public bool IsConnected
        {
            get { return isConnected; }
        }

        public void Initialize()
        {
            try
            {
                RealSerialPort realSerialPort = RealSerialPort.Instance(serialPortIndex);
                serialPortWrapper = new SerialPortWrapper(realSerialPort);                                
                isConnected = true;
                logger.Info(LogArduinoDeviceInitializationSucceeded + serialPortIndex);
            }
            catch (SerialPortException)
            {
                // SerialPortException is handled by our caller FLMScanController
                throw;
            }
        }

        public void StepClockwise()
        {
            if (currentPosition < maxPosition)
            {
                ++currentPosition;
            }
            else
            {
                currentPosition = minPosition;
            }

            Rotate(1);
        }

        public void StepClockwise(uint microsteps)
        {
            int distanceToMaxPosition = maxPosition - currentPosition;

            if (distanceToMaxPosition > microsteps)
            {
                currentPosition += (int)microsteps;
            }
            else
            {
                currentPosition = (int)microsteps - distanceToMaxPosition;
            }

            Rotate((int)microsteps);
        }

        public void StepCounterClockwise()
        {
            if (currentPosition > minPosition)
            {
                --currentPosition;
            }
            else
            {
                currentPosition = maxPosition;
            }

            Rotate(-1);
        }

        public void StepCounterClockwise(uint microsteps)
        {
            int positionDifference = currentPosition - (int)microsteps;

            if (positionDifference > 0)
            {
                currentPosition -= (int)microsteps;
            }
            else
            {
                currentPosition = maxPosition + positionDifference;
            }

            Rotate(-(int)microsteps);
        }

        public int CurrentPosition
        {
            get { return currentPosition; }
        }          

        private void Rotate(int microsteps)
        {
            backgroundWorker.RunWorkerAsync(microsteps); 
        }

        // BackgroundWorker work
        private void DoRotation(object sender, DoWorkEventArgs e)
        {
            int bufferSize = 2;
            char[] buffer = new char[bufferSize];

            int microsteps = (int)e.Argument;

            if (microsteps < 0)
            {
                buffer[0] = '-';
                buffer[1] = (char)(microsteps * -1);
            }
            else
            {
                buffer[0] = '+';
                buffer[1] = (char)microsteps;
            }

            try
            {
                serialPortWrapper.Write(buffer, 0, bufferSize);
            }
            catch (SerialPortException ex)
            {
                // we can't do a meaningful exception handling in this worker thread
                // therefore we simply log the exception
                logger.Error(LogArduinoFreeRotatorStepFailed + ex);                                
            }

            Thread.Sleep(1000);            
        }

        // BackgroundWorker callback on completion
        private void WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (StepDone != null)
            {
                StepDone(this, EventArgs.Empty);
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool isDisposing)
        {
            if (isDisposed)
                return;

            if (isDisposing)
            {
                isDisposed = true;                
            }
        }
    }
}
